﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using Gemli.Data.Linq;

namespace Gemli.Data.Providers
{
    ///////////////////////////////////
    // LINQ support


    public interface IDataModelQueryProvider
    {
        IQueryable<TModel> CreateQuery<TModel>(System.Linq.Expressions.Expression expression)
        where TModel : DataModel;

        TResult Execute<TModel, TResult>(System.Linq.Expressions.Expression expression)
        where TModel : DataModel;
    }

    public partial class DataProviderBase : IQueryProvider, IDataModelQueryProvider
    {

        /// <remarks>
        /// From http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx
        /// </remarks>
        internal static class TypeSystem
        {
            internal static Type GetElementType(Type seqType)
            {
                Type ienum = FindIEnumerable(seqType);
                if (ienum == null) return seqType;
                return ienum.GetGenericArguments()[0];
            }
            private static Type FindIEnumerable(Type seqType)
            {
                if (seqType == null || seqType == typeof(string))
                    return null;
                if (seqType.IsArray)
                    return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType());
                if (seqType.IsGenericType)
                {
                    foreach (Type arg in seqType.GetGenericArguments())
                    {
                        Type ienum = typeof(IEnumerable<>).MakeGenericType(arg);
                        if (ienum.IsAssignableFrom(seqType))
                        {
                            return ienum;
                        }
                    }
                }
                Type[] ifaces = seqType.GetInterfaces();
                if (ifaces != null && ifaces.Length > 0)
                {
                    foreach (Type iface in ifaces)
                    {
                        Type ienum = FindIEnumerable(iface);
                        if (ienum != null) return ienum;
                    }
                }
                if (seqType.BaseType != null && seqType.BaseType != typeof(object))
                {
                    return FindIEnumerable(seqType.BaseType);
                }
                return null;
            }
        }

        /// <summary>
        /// Constructs an <see cref="IQueryable&lt;T&gt;"/> object that can 
        /// evaluate the query represented by a specified expression tree.
        /// </summary>
        /// <typeparam name="TElement"></typeparam>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected internal virtual IQueryable<TElement> CreateQuery<TElement>(System.Linq.Expressions.Expression expression)
            where TElement : DataModel
        {
            return new DataModelQuery<TElement> { Expression = expression, Provider = this };
        }

        IQueryable<TElement> IDataModelQueryProvider.CreateQuery<TElement>(System.Linq.Expressions.Expression expression)
        {
            var ret = this.CreateQuery<TElement>(expression);
            ret = (new QueryTranslator<TElement> ((DataModelQuery<TElement>)ret)).Translate(expression) as IQueryable<TElement>;
            return ret;
        }

        IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(System.Linq.Expressions.Expression expression)
        {
            //try
            //{
                var pt = typeof(IDataModelQueryProvider).GetMethods().ToList().Find(m => m.Name.StartsWith("CreateQuery") && m.IsGenericMethod)
                    .MakeGenericMethod(typeof(TElement));
                var ret = pt.Invoke(this, new object[] { expression }) as IQueryable<TElement>;
                return ret;
            //}
            //catch (TargetInvocationException tie)
            //{
            //    throw tie.InnerException;
            //}
        }

        IQueryable IQueryProvider.CreateQuery(System.Linq.Expressions.Expression expression)
        {
            var elementType = TypeSystem.GetElementType(expression.Type);
            try
            {
                var pt = typeof(IDataModelQueryProvider).GetMethods().ToList().Find(m => m.Name.StartsWith("CreateQuery") && m.IsGenericMethod)
                    .MakeGenericMethod(elementType);
                return pt.Invoke(this, new object[] { expression }) as IQueryable;
            }
            catch (TargetInvocationException tie)
            {
                throw tie.InnerException;
            }
        }

        TResult IQueryProvider.Execute<TResult>(System.Linq.Expressions.Expression expression)
        {
            var elementType = TypeSystem.GetElementType(expression.Type);
            try
            {
                var pt = typeof(IDataModelQueryProvider).GetMethods().ToList()
                    .Find(m => m.Name.StartsWith("Execute") && m.IsGenericMethod && m.GetGenericArguments().Length == 2)
                    .MakeGenericMethod(elementType, typeof(TResult));
                return (TResult)pt.Invoke(this, new object[] { expression });
            }
            catch (TargetInvocationException tie)
            {
                throw tie.InnerException;
            }
        }

        object IQueryProvider.Execute(System.Linq.Expressions.Expression expression)
        {
            var elementType = TypeSystem.GetElementType(expression.Type);
            try
            {
                var pt = typeof(IDataModelQueryProvider).GetMethods().ToList()
                    .Find(m => m.Name.StartsWith("Execute") && m.IsGenericMethod && m.GetGenericArguments().Length == 2)
                    .MakeGenericMethod(elementType, typeof(object));
                return pt.Invoke(this, new object[] { expression });
            }
            catch (TargetInvocationException tie)
            {
                throw tie.InnerException;
            }
        }

        TResult IDataModelQueryProvider.Execute<TModel, TResult>(System.Linq.Expressions.Expression expression)

        {
            return this.Execute<TModel, TResult>(expression);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected internal virtual TResult Execute<TModel, TResult>(System.Linq.Expressions.Expression expression)
            where TModel : DataModel
        {
            var query = CreateQuery<TModel>(expression) as DataModelQuery<TModel>;
            query = (new QueryTranslator<TModel>(query)).Translate(expression);
            var results = query.SelectMany();
            if (typeof(TResult).IsAssignableFrom(results.GetType())) return (TResult)(object)results;
            throw new NotImplementedException("Cannot return type "
                + typeof(TResult).Name + " from " + results.GetType().Name);
        }
    }
}
